/*******************************************************************************
** Qt Advanced Docking System
** Copyright (C) 2017 Uwe Kindler
**
** This library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public
** License as published by the Free Software Foundation; either
** version 2.1 of the License, or (at your option) any later version.
**
** This library is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public
** License along with this library; If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/


//============================================================================
/// \file   AutoHideDockContainer.cpp
/// \author Syarif Fakhri
/// \date   05.09.2022
/// \brief  Implementation of CAutoHideDockContainer class
//============================================================================

//============================================================================
//                                   INCLUDES
//============================================================================
#include "AutoHideDockContainer.h"

#include <QXmlStreamWriter>
#include <QBoxLayout>
#include <QPainter>
#include <QSplitter>
#include <QPointer>
#include <QApplication>
#include <QCursor>

#include "DockManager.h"
#include "DockAreaWidget.h"
#include "ResizeHandle.h"
#include "DockComponentsFactory.h"
#include "AutoHideSideBar.h"
#include "AutoHideTab.h"


#include <iostream>

namespace ads
{
static const int ResizeMargin = 30;

// 判断是否为水平区域
bool static isHorizontalArea(SideBarLocation Area)
{
    switch (Area)
    {
    case SideBarLocation::SideBarTop:
    case SideBarLocation::SideBarBottom: 
        return true;
    case SideBarLocation::SideBarLeft:
    case SideBarLocation::SideBarRight: 
        return false;
    default:
        return true;
    }
    return true;
}

// 根据侧边栏位置获取边缘
Qt::Edge static edgeFromSideTabBarArea(SideBarLocation Area)
{
    switch (Area)
    {
    case SideBarLocation::SideBarTop: 
        return Qt::BottomEdge;
    case SideBarLocation::SideBarBottom: 
        return Qt::TopEdge;
    case SideBarLocation::SideBarLeft: 
        return Qt::RightEdge;
    case SideBarLocation::SideBarRight: 
        return Qt::LeftEdge;
    default:
        return Qt::LeftEdge;
    }
    return Qt::LeftEdge;
}

// 调整句柄布局位置
int resizeHandleLayoutPosition(SideBarLocation Area)
{
    switch (Area)
    {
    case SideBarLocation::SideBarBottom:
    case SideBarLocation::SideBarRight: 
        return 0;
    case SideBarLocation::SideBarTop:
    case SideBarLocation::SideBarLeft: 
        return 1;
    default:
        return 0;
    }
    return 0;
}

/**
 * CAutoHideDockContainer的私有数据 - pimpl
 */
struct AutoHideDockContainerPrivate
{
    CAutoHideDockContainer* _this;
    CDockAreaWidget* DockArea{ nullptr };
    CDockWidget* DockWidget{ nullptr };
    SideBarLocation SideTabBarArea = SideBarNone;
    QBoxLayout* Layout = nullptr;
    CResizeHandle* ResizeHandle = nullptr;
    QSize Size; // 创建无效的大小
    QPointer<CAutoHideTab> SideTab;
    QSize SizeCache;

    // 私有数据构造函数
    AutoHideDockContainerPrivate(CAutoHideDockContainer* _public);

    // 获取DockWidget区域的便捷函数
    DockWidgetArea getDockWidgetArea(SideBarLocation area)
    {
        switch (area)
        {
        case SideBarLocation::SideBarLeft: 
            return LeftDockWidgetArea;
        case SideBarLocation::SideBarRight: 
            return RightDockWidgetArea;
        case SideBarLocation::SideBarBottom: 
            return BottomDockWidgetArea;
        case SideBarLocation::SideBarTop: 
            return TopDockWidgetArea;
        default:
            return LeftDockWidgetArea;
        }
        return LeftDockWidgetArea;
    }

    // 更新调整句柄的大小限制
    void updateResizeHandleSizeLimitMax()
    {
        auto Rect = _this->dockContainer()->contentRect();
        const auto maxResizeHandleSize = ResizeHandle->orientation() == Qt::Horizontal ?
            Rect.width() : Rect.height();
        ResizeHandle->setMaxResizeSize(maxResizeHandleSize - ResizeMargin);
    }

    // 检查是否为水平区域的便捷函数
    bool isHorizontal() const
    {
        return isHorizontalArea(SideTabBarArea);
    }

    // 将事件转发给dock container
    void forwardEventToDockContainer(QEvent* event)
    {
        auto DockContainer = _this->dockContainer();
        if (DockContainer)
        {
            DockContainer->handleAutoHideWidgetEvent(event, _this);
        }
    }
}; // 结构体AutoHideDockContainerPrivate

AutoHideDockContainerPrivate::AutoHideDockContainerPrivate(CAutoHideDockContainer* _public) :
    _this(_public)
{
}

// 获取dock container
CDockContainerWidget* CAutoHideDockContainer::dockContainer() const
{
    return internal::findParent<CDockContainerWidget*>(this);
}

CAutoHideDockContainer::CAutoHideDockContainer(CDockWidget* DockWidget, SideBarLocation area, CDockContainerWidget* parent) :
    Super(parent),
    d(new AutoHideDockContainerPrivate(this))
{
    hide(); // 自动隐藏dock container初始状态为隐藏
    d->SideTabBarArea = area;
    d->SideTab = componentsFactory()->createDockWidgetSideTab(nullptr);
    connect(d->SideTab, &CAutoHideTab::pressed, this, &CAutoHideDockContainer::toggleCollapseState);
    d->DockArea = new CDockAreaWidget(DockWidget->dockManager(), parent);
    d->DockArea->setObjectName("autoHideDockArea");
    d->DockArea->setAutoHideDockContainer(this);
    setObjectName("autoHideDockContainer");
    d->Layout = new QBoxLayout(isHorizontalArea(area) ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight);
    d->Layout->setContentsMargins(0, 0, 0, 0);
    d->Layout->setSpacing(0);
    setLayout(d->Layout);
    d->ResizeHandle = new CResizeHandle(edgeFromSideTabBarArea(area), this);
    d->ResizeHandle->setMinResizeSize(64);
    bool OpaqueResize = CDockManager::testConfigFlag(CDockManager::OpaqueSplitterResize);
    d->ResizeHandle->setOpaqueResize(OpaqueResize);
    d->Size = d->DockArea->size();
    d->SizeCache = DockWidget->size();
    addDockWidget(DockWidget);
    parent->registerAutoHideWidget(this);
    // 在dock widget之前将dock area添加到布局中
    // 如果在dock widget之前将其添加到布局中，可能会看到针对OpenGL小部件或QAxWidgets的以下警告：
    // setGeometry: Unable to set geometry XxY+Width+Height on QWidgetWindow/'WidgetClassWindow
    d->Layout->addWidget(d->DockArea);
    d->Layout->insertWidget(resizeHandleLayoutPosition(area), d->ResizeHandle);
}

void CAutoHideDockContainer::updateSize()
{
    auto dockContainerParent = dockContainer();
    if (!dockContainerParent)
    {
        return;
    }
    auto rect = dockContainerParent->contentRect();
    switch (sideBarLocation())
    {
    case SideBarLocation::SideBarTop:
        resize(rect.width(), qMin(rect.height() - ResizeMargin, d->Size.height()));
        move(rect.topLeft());
        break;
    case SideBarLocation::SideBarLeft:
        resize(qMin(d->Size.width(), rect.width() - ResizeMargin), rect.height());
        move(rect.topLeft());
        break;
    case SideBarLocation::SideBarRight:
    {
        resize(qMin(d->Size.width(), rect.width() - ResizeMargin), rect.height());
        QPoint p = rect.topRight();
        p.rx() -= (width() - 1);
        move(p);
    }
    break;
    case SideBarLocation::SideBarBottom:
    {
        resize(rect.width(), qMin(rect.height() - ResizeMargin, d->Size.height()));
        QPoint p = rect.bottomLeft();
        p.ry() -= (height() - 1);
        move(p);
    }
    break;
    default:
        break;
    }
    if (orientation() == Qt::Horizontal)
    {
        d->SizeCache.setHeight(this->height());
    }
    else
    {
        d->SizeCache.setWidth(this->width());
    }
}

CAutoHideDockContainer::~CAutoHideDockContainer()
{
    ADS_PRINT("~CAutoHideDockContainer");
    // 移除事件过滤器以防有任何排队的消息
    qApp->removeEventFilter(this);
    if (dockContainer())
    {
        dockContainer()->removeAutoHideWidget(this);
    }
    if (d->SideTab)
    {
        delete d->SideTab;
    }
    delete d;
}

//============================================================================
// 返回自动隐藏侧边栏
CAutoHideSideBar* CAutoHideDockContainer::autoHideSideBar() const
{
    if (d->SideTab)
    {
        return d->SideTab->sideBar();
    }
    else
    {
        auto DockContainer = dockContainer();
        return DockContainer ? DockContainer->autoHideSideBar(d->SideTabBarArea) : nullptr;
    }
}
//============================================================================
// 返回自动隐藏标签
CAutoHideTab* CAutoHideDockContainer::autoHideTab() const
{
    return d->SideTab;
}
//============================================================================
// 返回停靠窗口
CDockWidget* CAutoHideDockContainer::dockWidget() const
{
    return d->DockWidget;
}
//============================================================================
// 添加停靠窗口
void CAutoHideDockContainer::addDockWidget(CDockWidget* DockWidget)
{
    if (d->DockWidget)
    {
        // 移除旧的停靠窗口
        d->DockArea->removeDockWidget(d->DockWidget);
    }
    d->DockWidget = DockWidget;
    d->SideTab->setDockWidget(DockWidget);
    CDockAreaWidget* OldDockArea = DockWidget->dockAreaWidget();
    auto IsRestoringState = DockWidget->dockManager()->isRestoringState();
    if (OldDockArea && !IsRestoringState)
    {
        // 初始大小应稍大于原始停靠区大小，以防止此自动隐藏停靠区的调整大小句柄靠近旧停靠区的分隔条
        d->Size = OldDockArea->size() + QSize(16, 16);
        OldDockArea->removeDockWidget(DockWidget);
    }
    d->DockArea->addDockWidget(DockWidget);
    updateSize();
    // 停靠区不可见且在调用updateSize()时不会更新大小。因此在此处显式调整大小。
    // 一旦它可见，它将获得正确的大小
    d->DockArea->resize(size());
}
//============================================================================
// 返回侧边栏位置
SideBarLocation CAutoHideDockContainer::sideBarLocation() const
{
    return d->SideTabBarArea;
}
//============================================================================
// 设置侧边栏位置
void CAutoHideDockContainer::setSideBarLocation(SideBarLocation SideBarLocation)
{
    if (d->SideTabBarArea == SideBarLocation)
    {
        return;
    }
    d->SideTabBarArea = SideBarLocation;
    d->Layout->removeWidget(d->ResizeHandle);
    d->Layout->setDirection(isHorizontalArea(SideBarLocation) ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight);
    d->Layout->insertWidget(resizeHandleLayoutPosition(SideBarLocation), d->ResizeHandle);
    d->ResizeHandle->setHandlePosition(edgeFromSideTabBarArea(SideBarLocation));
    internal::repolishStyle(this, internal::RepolishDirectChildren);
}
//============================================================================
// 返回停靠区窗口
CDockAreaWidget* CAutoHideDockContainer::dockAreaWidget() const
{
    return d->DockArea;
}
//============================================================================
// 将内容移动到父窗口
void CAutoHideDockContainer::moveContentsToParent()
{
    cleanupAndDelete();
    // 如果取消固定自动隐藏停靠窗口，则将其插入与其作为自动隐藏窗口时相同的位置。
    // 这样做对用户来说最不令人惊讶，他不必搜索窗口插入的位置。
    d->DockWidget->setDockArea(nullptr);
    auto DockContainer = dockContainer();
    DockContainer->addDockWidget(d->getDockWidgetArea(d->SideTabBarArea), d->DockWidget);
}
//============================================================================
// 清理并删除
void CAutoHideDockContainer::cleanupAndDelete()
{
    const auto dockWidget = d->DockWidget;
    if (dockWidget)
    {
        auto SideTab = d->SideTab;
        SideTab->removeFromSideBar();
        SideTab->setParent(nullptr);
        SideTab->hide();
    }
    hide();
    deleteLater();
}
//============================================================================
// 保存状态
void CAutoHideDockContainer::saveState(QXmlStreamWriter& s)
{
    s.writeStartElement("Widget");
    s.writeAttribute("Name", d->DockWidget->objectName());
    s.writeAttribute("Closed", QString::number(d->DockWidget->isClosed() ? 1 : 0));
    s.writeAttribute("Size", QString::number(d->isHorizontal() ? d->Size.height() : d->Size.width()));
    s.writeEndElement();
}
//============================================================================
// 切换视图
void CAutoHideDockContainer::toggleView(bool Enable)
{
    if (Enable)
    {
        if (d->SideTab)
        {
            d->SideTab->show();
        }
    }
    else
    {
        if (d->SideTab)
        {
            d->SideTab->hide();
        }
        hide();
        qApp->removeEventFilter(this);
    }
}
//============================================================================
// 折叠视图
void CAutoHideDockContainer::collapseView(bool Enable)
{
    if (Enable)
    {
        hide();
        qApp->removeEventFilter(this);
    }
    else
    {
        updateSize();
        d->updateResizeHandleSizeLimitMax();
        raise();
        show();
        d->DockWidget->dockManager()->setDockWidgetFocused(d->DockWidget);
        qApp->installEventFilter(this);
    }
    ADS_PRINT("CAutoHideDockContainer::collapseView " << Enable);
    d->SideTab->updateStyle();
}
//============================================================================
// 切换折叠状态
void CAutoHideDockContainer::toggleCollapseState()
{
    collapseView(isVisible());
}
//============================================================================
// 设置大小
void CAutoHideDockContainer::setSize(int Size)
{
    if (d->isHorizontal())
    {
        d->Size.setHeight(Size);
    }
    else
    {
        d->Size.setWidth(Size);
    }
    updateSize();
}
//============================================================================
// 返回祖先是否为后代的祖先对象
static bool objectIsAncestorOf(const QObject* descendant, const QObject* ancestor)
{
    if (!ancestor)
    {
        return false;
    }
    while (descendant)
    {
        if (descendant == ancestor)
        {
            return true;
        }
        descendant = descendant->parent();
    }
    return false;
}
//============================================================================
// 返回对象是否为后代或祖先对象
static bool isObjectOrAncestor(const QObject *descendant, const QObject *ancestor)
{
    if (ancestor && (descendant == ancestor))
    {
        return true;
    }
    else
    {
        return objectIsAncestorOf(descendant, ancestor);
    }
}


//============================================================================ 
bool CAutoHideDockContainer::eventFilter(QObject* watched, QEvent* event) 
{ 
    // 使用switch case语句会更好，但我们不能在switch case中使用internal::FloatingWidgetDragStartEvent 
    if (event->type() == QEvent::Resize) 
    { 
        if (!d->ResizeHandle->isResizing()) 
        { 
            updateSize(); 
        } 
    } 
    else if (event->type() == QEvent::MouseButtonPress) 
    { 
        auto widget = qobject_cast<QWidget*>(watched); 
        // 忽略非部件事件 
        if (!widget) 
        { 
            return Super::eventFilter(watched, event); 
        } 
        // 检查用户是否单击了侧边选项卡，并忽略此事件，因为侧边选项卡单击处理程序将调用collapseView()。 
        // 如果我们在这里不忽略它，那么我们将折叠容器，而侧边选项卡单击处理程序将展开它 
        if (widget == d->SideTab.data()) 
        { 
            return Super::eventFilter(watched, event); 
        } 
        // 现在我们检查用户是否单击了此自动隐藏容器内部。 
        // 如果单击在此自动隐藏容器内部，则可以忽略事件，因为如果用户在其中工作，则不应折叠自动隐藏叠加层 
        if (isObjectOrAncestor(widget, this)) 
        { 
            return Super::eventFilter(watched, event); 
        } 
        // 如果单击不在此容器内，则忽略鼠标单击 
        if (!isObjectOrAncestor(widget, dockContainer())) 
        { 
            return Super::eventFilter(watched, event); 
        } 
        // 用户单击了容器 - 折叠自动隐藏小部件 
        collapseView(true); 
    } 
    else if (event->type() == internal::FloatingWidgetDragStartEvent) 
    { 
        // 如果我们正在拖动自己的浮动窗口，则不需要折叠视图 
        auto FloatingWidget = dockContainer()->floatingWidget(); 
        if (FloatingWidget != watched) 
        { 
            collapseView(true); 
        } 
    } 
    else if (event->type() == internal::DockedWidgetDragStartEvent) 
    { 
        collapseView(true); 
    } 
    return Super::eventFilter(watched, event); 
} 
//============================================================================ 
void CAutoHideDockContainer::resizeEvent(QResizeEvent* event) 
{ 
    Super::resizeEvent(event); 
    if (d->ResizeHandle->isResizing()) 
    { 
        d->Size = this->size(); 
        d->updateResizeHandleSizeLimitMax(); 
    } 
} 
//============================================================================ 
void CAutoHideDockContainer::leaveEvent(QEvent *event) 
{ 
    // 在不透明模式下通过调整大小手柄调整停靠容器的大小可能会导致不真正的离开事件。因此， 
    // 我们在这里检查是否真正超出了我们的矩形。 
    auto pos = mapFromGlobal(QCursor::pos()); 
    if (!rect().contains(pos)) 
    { 
        d->forwardEventToDockContainer(event); 
    } 
    Super::leaveEvent(event); 
} 
//============================================================================ 
bool CAutoHideDockContainer::event(QEvent* event) 
{ 
    switch (event->type()) 
    { 
    case QEvent::Enter: 
    case QEvent::Hide: 
        d->forwardEventToDockContainer(event); 
        break; 
    case QEvent::MouseButtonPress: 
        return true; 
        break; 
    default: 
        break; 
    } 
    return Super::event(event); 
} 
//============================================================================ 
Qt::Orientation CAutoHideDockContainer::orientation() const 
{ 
    return ads::internal::isHorizontalSideBarLocation(d->SideTabBarArea) 
        ? Qt::Horizontal : Qt::Vertical; 
} 
//============================================================================ 
void CAutoHideDockContainer::resetToInitialDockWidgetSize() 
{ 
    if (orientation() == Qt::Horizontal) 
    { 
        setSize(d->SizeCache.height()); 
    } 
    else 
    { 
        setSize(d->SizeCache.width()); 
    } 
} 
//============================================================================ 
void CAutoHideDockContainer::moveToNewSideBarLocation(SideBarLocation NewSideBarLocation, 
    int TabIndex) 
{ 
    if (NewSideBarLocation == sideBarLocation() && TabIndex == this->tabIndex()) 
    { 
        return; 
    } 
    auto OldOrientation = orientation(); 
    auto SideBar = dockContainer()->autoHideSideBar(NewSideBarLocation); 
    SideBar->addAutoHideWidget(this, TabIndex); 
    // 如果我们将水平自动隐藏容器移动到垂直位置，则将其调整为原始停靠窗口大小，以避免插入后极度拉伸的停靠窗口 
    if (SideBar->orientation() != OldOrientation) 
    { 
        resetToInitialDockWidgetSize(); 
    } 
} 
//============================================================================ 
int CAutoHideDockContainer::tabIndex() const 
{ 
    return d->SideTab->tabIndex(); 
} 

}

